/**
 * Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
 * Distributed under the MIT License (http://opensource.org/licenses/MIT)
 */

#pragma once

#include "quill/detail/LogManager.h"
#include "quill/detail/misc/Attributes.h"
#include "quill/detail/misc/Rdtsc.h"
#include <chrono>

namespace quill
{
/**
 * This class is useful when `default_timestamp_clock_type = TimestampClockType::Tsc` is used and
 * you want to take synchronised timestamps with the logger.
 *
 * The backend logging thread is running an RdtscClock that frequently synchronises with
 * the system's wall clock. It then converts the rdtsc values of the hot threads to wall clock
 * timestamps.
 *
 * This class gives you access to the instance of the clock that is used by the backend logging thread.
 *
 * When TimestampClockType::Tsc in Config.h is not used, then this class will revert to using
 * the system clock for timestamps
 *
 * @note If you want more accurate timestamps consider reducing rdtsc_resync_interval in Config.h
 * @note all the methods of the class are thread-safe
 */
class Clock
{
public:
  class RdtscVal
  {
  public:
    RdtscVal(RdtscVal const& other) = default;
    RdtscVal(RdtscVal&& other) noexcept = default;
    RdtscVal& operator=(RdtscVal const& other) = default;
    RdtscVal& operator=(RdtscVal&& other) noexcept = default;

    QUILL_NODISCARD_ALWAYS_INLINE_HOT uint64_t value() const noexcept { return _value; }

  private:
    RdtscVal() noexcept : _value(detail::rdtsc()) {}
    friend Clock;

    uint64_t _value;
  };

public:
  using duration = std::chrono::nanoseconds;
  using rep = duration::rep;
  using period = duration::period;
  using time_point = std::chrono::time_point<Clock, duration>;
  static constexpr bool is_steady = false;

  /**
   * Provides current timestamp calculated via using the TSC counter. The timestamp comes from
   * the same source and is in sync with the timestamps generated by the backend logging
   * thread in the log file
   * @return a wall clock timestamp in nanoseconds since epoch
   */
  QUILL_NODISCARD QUILL_ATTRIBUTE_HOT static time_point now() noexcept
  {
    uint64_t const ts =
      detail::LogManagerSingleton::instance().log_manager().time_since_epoch(detail::rdtsc());

    return ts ? time_point{std::chrono::nanoseconds{ts}}
              : time_point{std::chrono::nanoseconds{
                  std::chrono::time_point_cast<std::chrono::nanoseconds>(std::chrono::system_clock::now())
                    .time_since_epoch()
                    .count()}};
  }

  /**
   * Returns the TSC counter. The return value from this function can be passed to `to_time_point`
   * @return the tsc counter
   */
  QUILL_NODISCARD QUILL_ATTRIBUTE_HOT static RdtscVal rdtsc() noexcept { return RdtscVal{}; }

  /**
   * Converts a TSC counter value to a wall clock timestamp.
   * @note storing an rdtsc value and then passing it to this function with a delay will
   * result a less accurate result
   * @param rdtsc the rdtsc value to convert
   * @warning this function will return `0` when TimestampClockType::Tsc is not enabled in Config.h
   * @return time since epoch in nanoseconds
   */
  QUILL_NODISCARD QUILL_ATTRIBUTE_HOT static time_point to_time_point(RdtscVal rdtsc) noexcept
  {
    return time_point{std::chrono::nanoseconds{
      detail::LogManagerSingleton::instance().log_manager().time_since_epoch(rdtsc.value())}};
  }
};
} // namespace quill